<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2012 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
// $Id: AdvModel.class.php 2702 2012-02-02 12:35:01Z liu21st $

/**
 +------------------------------------------------------------------------------
 * ThinkPHP 高级模型类扩展
 +------------------------------------------------------------------------------
 */
class AdvModel extends Model {
	protected $optimLock = 'lock_version';
	protected $returnType  =  'array';
	protected $blobFields     =   array();
	protected $blobValues    = null;
	protected $serializeField   = array();
	protected $readonlyField  = array();
	protected $_filter           = array();

	public function __construct($name='',$tablePrefix='',$connection='') {
		if('' !== $name || is_subclass_of($this,'AdvModel') ){
			// 如果是AdvModel子类或者有传入模型名称则获取字段缓存
		}else{
			// 空的模型 关闭字段缓存
			$this->autoCheckFields = false;
		}
		parent::__construct($name,$tablePrefix,$connection);
	}

	/**
	 +----------------------------------------------------------
	 * 利用__call方法重载 实现一些特殊的Model方法 （魔术方法）
	 +----------------------------------------------------------
	 * @access public
	 +----------------------------------------------------------
	 * @param string $method 方法名称
	 * @param mixed $args 调用参数
	 +----------------------------------------------------------
	 * @return mixed
	 +----------------------------------------------------------
	 */
	public function __call($method,$args) {
		if(strtolower(substr($method,0,3))=='top'){
			// 获取前N条记录
			$count = substr($method,3);
			array_unshift($args,$count);
			return call_user_func_array(array(&$this, 'topN'), $args);
		}else{
			return parent::__call($method,$args);
		}
	}

	/**
	 +----------------------------------------------------------
	 * 对保存到数据库的数据进行处理
	 +----------------------------------------------------------
	 * @access protected
	 +----------------------------------------------------------
	 * @param mixed $data 要操作的数据
	 +----------------------------------------------------------
	 * @return boolean
	 +----------------------------------------------------------
	 */
	protected function _facade($data) {
		// 检查序列化字段
		$data = $this->serializeField($data);
		return parent::_facade($data);
	}

	// 查询成功后的回调方法
	protected function _after_find(&$result,$options='') {
		// 检查序列化字段
		$this->checkSerializeField($result);
		// 获取文本字段
		$this->getBlobFields($result);
		// 检查字段过滤
		$result   =  $this->getFilterFields($result);
		// 缓存乐观锁
		$this->cacheLockVersion($result);
	}

	// 查询数据集成功后的回调方法
	protected function _after_select(&$resultSet,$options='') {
		// 检查序列化字段
		$resultSet   =  $this->checkListSerializeField($resultSet);
		// 获取文本字段
		$resultSet   =  $this->getListBlobFields($resultSet);
		// 检查列表字段过滤
		$resultSet   =  $this->getFilterListFields($resultSet);
	}

	// 写入前的回调方法
	protected function _before_insert(&$data,$options='') {
		// 记录乐观锁
		$data = $this->recordLockVersion($data);
		// 检查文本字段
		$data = $this->checkBlobFields($data);
		// 检查字段过滤
		$data   =  $this->setFilterFields($data);
	}

	protected function _after_insert($data,$options) {
		// 保存文本字段
		$this->saveBlobFields($data);
	}

	// 更新前的回调方法
	protected function _before_update(&$data,$options='') {
		// 检查乐观锁
		if(!$this->checkLockVersion($data,$options)) {
			return false;
		}
		// 检查文本字段
		$data = $this->checkBlobFields($data);
		// 检查只读字段
		$data = $this->checkReadonlyField($data);
		// 检查字段过滤
		$data   =  $this->setFilterFields($data);
	}

	protected function _after_update($data,$options) {
		// 保存文本字段
		$this->saveBlobFields($data);
	}

	protected function _after_delete($data,$options) {
		// 删除Blob数据
		$this->delBlobFields($data);
	}

	/**
	 +----------------------------------------------------------
	 * 记录乐观锁
	 +----------------------------------------------------------
	 * @access protected
	 +----------------------------------------------------------
	 * @param array $data 数据对象
	 +----------------------------------------------------------
	 * @return array
	 +----------------------------------------------------------
	 */
	protected function recordLockVersion($data) {
		// 记录乐观锁
		if($this->optimLock && !isset($data[$this->optimLock]) ) {
			if(in_array($this->optimLock,$this->fields,true)) {
				$data[$this->optimLock]  =   0;
			}
		}
		return $data;
	}

	/**
	 +----------------------------------------------------------
	 * 缓存乐观锁
	 +----------------------------------------------------------
	 * @access protected
	 +----------------------------------------------------------
	 * @param array $data 数据对象
	 +----------------------------------------------------------
	 * @return void
	 +----------------------------------------------------------
	 */
	protected function cacheLockVersion($data) {
		if($this->optimLock) {
			if(isset($data[$this->optimLock]) && isset($data[$this->getPk()])) {
				// 只有当存在乐观锁字段和主键有值的时候才记录乐观锁
				$_SESSION[$this->name.'_'.$data[$this->getPk()].'_lock_version']    =   $data[$this->optimLock];
			}
		}
	}

	/**
	 +----------------------------------------------------------
	 * 检查乐观锁
	 +----------------------------------------------------------
	 * @access protected
	 +----------------------------------------------------------
	 * @param array $data  当前数据
	 * @param array $options 查询表达式
	 +----------------------------------------------------------
	 * @return mixed
	 +----------------------------------------------------------
	 */
	protected function checkLockVersion(&$data,$options) {
		$id = $data[$this->getPk()];
		// 检查乐观锁
		$identify   = $this->name.'_'.$id.'_lock_version';
		if($this->optimLock && isset($_SESSION[$identify])) {
			$lock_version = $_SESSION[$identify];
			$vo   =  $this->field($this->optimLock)->find($id);
			$_SESSION[$identify]     =   $lock_version;
			$curr_version = $vo[$this->optimLock];
			if(isset($curr_version)) {
				if($curr_version>0 && $lock_version != $curr_version) {
					// 记录已经更新
					$this->error = L('_RECORD_HAS_UPDATE_');
					return false;
				}else{
					// 更新乐观锁
					$save_version = $data[$this->optimLock];
					if($save_version != $lock_version+1) {
						$data[$this->optimLock]  =   $lock_version+1;
					}
					$_SESSION[$identify]     =   $lock_version+1;
				}
			}
		}
		return true;
	}

	/**
	 +----------------------------------------------------------
	 * 查找前N个记录
	 +----------------------------------------------------------
	 * @access public
	 +----------------------------------------------------------
	 * @param integer $count 记录个数
	 * @param array $options 查询表达式
	 +----------------------------------------------------------
	 * @return array
	 +----------------------------------------------------------
	 */
	public function topN($count,$options=array()) {
		$options['limit'] =  $count;
		return $this->select($options);
	}

	/**
	 +----------------------------------------------------------
	 * 查询符合条件的第N条记录
	 * 0 表示第一条记录 -1 表示最后一条记录
	 +----------------------------------------------------------
	 * @access public
	 +----------------------------------------------------------
	 * @param integer $position 记录位置
	 * @param array $options 查询表达式
	 +----------------------------------------------------------
	 * @return mixed
	 +----------------------------------------------------------
	 */
	public function getN($position=0,$options=array()) {
		if($position>=0) { // 正向查找
			$options['limit'] = $position.',1';
			$list   =  $this->select($options);
			return $list?$list[0]:false;
		}else{ // 逆序查找
			$list   =  $this->select($options);
			return $list?$list[count($list)-abs($position)]:false;
		}
	}

	/**
	 +----------------------------------------------------------
	 * 获取满足条件的第一条记录
	 +----------------------------------------------------------
	 * @access public
	 +----------------------------------------------------------
	 * @param array $options 查询表达式
	 +----------------------------------------------------------
	 * @return mixed
	 +----------------------------------------------------------
	 */
	public function first($options=array()) {
		return $this->getN(0,$options);
	}

	/**
	 +----------------------------------------------------------
	 * 获取满足条件的最后一条记录
	 +----------------------------------------------------------
	 * @access public
	 +----------------------------------------------------------
	 * @param array $options 查询表达式
	 +----------------------------------------------------------
	 * @return mixed
	 +----------------------------------------------------------
	 */
	public function last($options=array()) {
		return $this->getN(-1,$options);
	}

	/**
	 +----------------------------------------------------------
	 * 返回数据
	 +----------------------------------------------------------
	 * @access public
	 +----------------------------------------------------------
	 * @param array $data 数据
	 * @param string $type 返回类型 默认为数组
	 +----------------------------------------------------------
	 * @return mixed
	 +----------------------------------------------------------
	 */
	public function returnResult($data,$type='') {
		if('' === $type)
		$type = $this->returnType;
		switch($type) {
			case 'array' :  return $data;
			case 'object':  return (object)$data;
			default:// 允许用户自定义返回类型
				if(class_exists($type))
				return new $type($data);
				else
				throw_exception(L('_CLASS_NOT_EXIST_').':'.$type);
		}
	}

	/**
	 +----------------------------------------------------------
	 * 获取数据的时候过滤数据字段
	 +----------------------------------------------------------
	 * @access protected
	 +----------------------------------------------------------
	 * @param mixed $result 查询的数据
	 +----------------------------------------------------------
	 * @return array
	 +----------------------------------------------------------
	 */
	protected function getFilterFields(&$result) {
		if(!empty($this->_filter)) {
			foreach ($this->_filter as $field=>$filter){
				if(isset($result[$field])) {
					$fun  =  $filter[1];
					if(!empty($fun)) {
						if(isset($filter[2]) && $filter[2]){
							// 传递整个数据对象作为参数
							$result[$field]  =  call_user_func($fun,$result);
						}else{
							// 传递字段的值作为参数
							$result[$field]  =  call_user_func($fun,$result[$field]);
						}
					}
				}
			}
		}
		return $result;
	}

	protected function getFilterListFields(&$resultSet) {
		if(!empty($this->_filter)) {
			foreach ($resultSet as $key=>$result)
			$resultSet[$key]  =  $this->getFilterFields($result);
		}
		return $resultSet;
	}

	/**
	 +----------------------------------------------------------
	 * 写入数据的时候过滤数据字段
	 +----------------------------------------------------------
	 * @access pubic
	 +----------------------------------------------------------
	 * @param mixed $result 查询的数据
	 +----------------------------------------------------------
	 * @return array
	 +----------------------------------------------------------
	 */
	protected function setFilterFields($data) {
		if(!empty($this->_filter)) {
			foreach ($this->_filter as $field=>$filter){
				if(isset($data[$field])) {
					$fun              =  $filter[0];
					if(!empty($fun)) {
						if(isset($filter[2]) && $filter[2]) {
							// 传递整个数据对象作为参数
							$data[$field]   =  call_user_func($fun,$data);
						}else{
							// 传递字段的值作为参数
							$data[$field]   =  call_user_func($fun,$data[$field]);
						}
					}
				}
			}
		}
		return $data;
	}

	/**
	 +----------------------------------------------------------
	 * 返回数据列表
	 +----------------------------------------------------------
	 * @access protected
	 +----------------------------------------------------------
	 * @param array $resultSet 数据
	 * @param string $type 返回类型 默认为数组
	 +----------------------------------------------------------
	 * @return void
	 +----------------------------------------------------------
	 */
	protected function returnResultSet(&$resultSet,$type='') {
		foreach ($resultSet as $key=>$data)
		$resultSet[$key]  =  $this->returnResult($data,$type);
		return $resultSet;
	}

	protected function checkBlobFields(&$data) {
		// 检查Blob文件保存字段
		if(!empty($this->blobFields)) {
			foreach ($this->blobFields as $field){
				if(isset($data[$field])) {
					if(isset($data[$this->getPk()]))
					$this->blobValues[$this->name.'/'.$data[$this->getPk()].'_'.$field] =   $data[$field];
					else
					$this->blobValues[$this->name.'/@?id@_'.$field] =   $data[$field];
					unset($data[$field]);
				}
			}
		}
		return $data;
	}

	/**
	 +----------------------------------------------------------
	 * 获取数据集的文本字段
	 +----------------------------------------------------------
	 * @access protected
	 +----------------------------------------------------------
	 * @param mixed $resultSet 查询的数据
	 * @param string $field 查询的字段
	 +----------------------------------------------------------
	 * @return void
	 +----------------------------------------------------------
	 */
	protected function getListBlobFields(&$resultSet,$field='') {
		if(!empty($this->blobFields)) {
			foreach ($resultSet as $key=>$result){
				$result =   $this->getBlobFields($result,$field);
				$resultSet[$key]    =   $result;
			}
		}
		return $resultSet;
	}

	/**
	 +----------------------------------------------------------
	 * 获取数据的文本字段
	 +----------------------------------------------------------
	 * @access protected
	 +----------------------------------------------------------
	 * @param mixed $data 查询的数据
	 * @param string $field 查询的字段
	 +----------------------------------------------------------
	 * @return void
	 +----------------------------------------------------------
	 */
	protected function getBlobFields(&$data,$field='') {
		if(!empty($this->blobFields)) {
			$pk =   $this->getPk();
			$id =   $data[$pk];
			if(empty($field)) {
				foreach ($this->blobFields as $field){
					$identify   =   $this->name.'/'.$id.'_'.$field;
					$data[$field]   =   F($identify);
				}
				return $data;
			}else{
				$identify   =   $this->name.'/'.$id.'_'.$field;
				return F($identify);
			}
		}
	}

	/**
	 +----------------------------------------------------------
	 * 保存File方式的字段
	 +----------------------------------------------------------
	 * @access protected
	 +----------------------------------------------------------
	 * @param mixed $data 保存的数据
	 +----------------------------------------------------------
	 * @return void
	 +----------------------------------------------------------
	 */
	protected function saveBlobFields(&$data) {
		if(!empty($this->blobFields)) {
			foreach ($this->blobValues as $key=>$val){
				if(strpos($key,'@?id@'))
				$key    =   str_replace('@?id@',$data[$this->getPk()],$key);
				F($key,$val);
			}
		}
	}

	/**
	 +----------------------------------------------------------
	 * 删除File方式的字段
	 +----------------------------------------------------------
	 * @access protected
	 +----------------------------------------------------------
	 * @param mixed $data 保存的数据
	 * @param string $field 查询的字段
	 +----------------------------------------------------------
	 * @return void
	 +----------------------------------------------------------
	 */
	protected function delBlobFields(&$data,$field='') {
		if(!empty($this->blobFields)) {
			$pk =   $this->getPk();
			$id =   $data[$pk];
			if(empty($field)) {
				foreach ($this->blobFields as $field){
					$identify   =   $this->name.'/'.$id.'_'.$field;
					F($identify,null);
				}
			}else{
				$identify   =   $this->name.'/'.$id.'_'.$field;
				F($identify,null);
			}
		}
	}

	/**
	 +----------------------------------------------------------
	 * 字段值延迟增长
	 +----------------------------------------------------------
	 * @access public
	 +----------------------------------------------------------
	 * @param string $field  字段名
	 * @param integer $step  增长值
	 * @param integer $lazyTime  延时时间(s)
	 +----------------------------------------------------------
	 * @return boolean
	 +----------------------------------------------------------
	 */
	public function setLazyInc($field,$step=1,$lazyTime=0) {
		$condition   =  $this->options['where'];
		if(empty($condition)) { // 没有条件不做任何更新
			return false;
		}
		if($lazyTime>0) {// 延迟写入
			$guid =  md5($this->name.'_'.$field.'_'.serialize($condition));
			$step = $this->lazyWrite($guid,$step,$lazyTime);
			if(false === $step ) return true; // 等待下次写入
		}
		return $this->setField($field,array('exp',$field.'+'.$step));
	}

	/**
	 +----------------------------------------------------------
	 * 字段值延迟减少
	 +----------------------------------------------------------
	 * @access public
	 +----------------------------------------------------------
	 * @param string $field  字段名
	 * @param integer $step  减少值
	 * @param integer $lazyTime  延时时间(s)
	 +----------------------------------------------------------
	 * @return boolean
	 +----------------------------------------------------------
	 */
	public function setLazyDec($field,$step=1,$lazyTime=0) {
		$condition   =  $this->options['where'];
		if(empty($condition)) { // 没有条件不做任何更新
			return false;
		}
		if($lazyTime>0) {// 延迟写入
			$guid =  md5($this->name.'_'.$field.'_'.serialize($condition));
			$step = $this->lazyWrite($guid,$step,$lazyTime);
			if(false === $step ) return true; // 等待下次写入
		}
		return $this->setField($field,array('exp',$field.'-'.$step));
	}

	/**
	 +----------------------------------------------------------
	 * 延时更新检查 返回false表示需要延时
	 * 否则返回实际写入的数值
	 +----------------------------------------------------------
	 * @access public
	 +----------------------------------------------------------
	 * @param string $guid  写入标识
	 * @param integer $step  写入步进值
	 * @param integer $lazyTime  延时时间(s)
	 +----------------------------------------------------------
	 * @return false|integer
	 +----------------------------------------------------------
	 */
	protected function lazyWrite($guid,$step,$lazyTime) {
		if(false !== ($value = F($guid))) { // 存在缓存写入数据
			if(time()>F($guid.'_time')+$lazyTime) {
				// 延时更新时间到了，删除缓存数据 并实际写入数据库
				F($guid,NULL);
				F($guid.'_time',NULL);
				return $value+$step;
			}else{
				// 追加数据到缓存
				F($guid,$value+$step);
				return false;
			}
		}else{ // 没有缓存数据
			F($guid,$step);
			// 计时开始
			F($guid.'_time',time());
			return false;
		}
	}

	/**
	 +----------------------------------------------------------
	 * 检查序列化数据字段
	 +----------------------------------------------------------
	 * @access protected
	 +----------------------------------------------------------
	 * @param array $data 数据
	 +----------------------------------------------------------
	 * @return array
	 +----------------------------------------------------------
	 */
	protected function serializeField(&$data) {
		// 检查序列化字段
		if(!empty($this->serializeField)) {
			// 定义方式  $this->serializeField = array('ser'=>array('name','email'));
			foreach ($this->serializeField as $key=>$val){
				if(empty($data[$key])) {
					$serialize  =   array();
					foreach ($val as $name){
						if(isset($data[$name])) {
							$serialize[$name]   =   $data[$name];
							unset($data[$name]);
						}
					}
					if(!empty($serialize)) {
						$data[$key] =   serialize($serialize);
					}
				}
			}
		}
		return $data;
	}

	// 检查返回数据的序列化字段
	protected function checkSerializeField(&$result) {
		// 检查序列化字段
		if(!empty($this->serializeField)) {
			foreach ($this->serializeField as $key=>$val){
				if(isset($result[$key])) {
					$serialize   =   unserialize($result[$key]);
					foreach ($serialize as $name=>$value)
					$result[$name]  =   $value;
					unset($serialize,$result[$key]);
				}
			}
		}
		return $result;
	}

	// 检查数据集的序列化字段
	protected function checkListSerializeField(&$resultSet) {
		// 检查序列化字段
		if(!empty($this->serializeField)) {
			foreach ($this->serializeField as $key=>$val){
				foreach ($resultSet as $k=>$result){
					if(isset($result[$key])) {
						$serialize   =   unserialize($result[$key]);
						foreach ($serialize as $name=>$value)
						$result[$name]  =   $value;
						unset($serialize,$result[$key]);
						$resultSet[$k] =   $result;
					}
				}
			}
		}
		return $resultSet;
	}

	/**
	 +----------------------------------------------------------
	 * 检查只读字段
	 +----------------------------------------------------------
	 * @access protected
	 +----------------------------------------------------------
	 * @param array $data 数据
	 +----------------------------------------------------------
	 * @return array
	 +----------------------------------------------------------
	 */
	protected function checkReadonlyField(&$data) {
		if(!empty($this->readonlyField)) {
			foreach ($this->readonlyField as $key=>$field){
				if(isset($data[$field]))
				unset($data[$field]);
			}
		}
		return $data;
	}

	/**
	 +----------------------------------------------------------
	 * 批处理执行SQL语句
	 * 批处理的指令都认为是execute操作
	 +----------------------------------------------------------
	 * @access public
	 +----------------------------------------------------------
	 * @param array $sql  SQL批处理指令
	 +----------------------------------------------------------
	 * @return boolean
	 +----------------------------------------------------------
	 */
	public function patchQuery($sql=array()) {
		if(!is_array($sql)) return false;
		// 自动启动事务支持
		$this->startTrans();
		try{
			foreach ($sql as $_sql){
				$result   =  $this->execute($_sql);
				if(false === $result) {
					// 发生错误自动回滚事务
					$this->rollback();
					return false;
				}
			}
			// 提交事务
			$this->commit();
		} catch (ThinkException $e) {
			$this->rollback();
		}
		return true;
	}

	/**
	 +----------------------------------------------------------
	 * 得到分表的的数据表名
	 +----------------------------------------------------------
	 * @access public
	 +----------------------------------------------------------
	 * @param array $data 操作的数据
	 +----------------------------------------------------------
	 * @return string
	 +----------------------------------------------------------
	 */
	public function getPartitionTableName($data=array()) {
		// 对数据表进行分区
		if(isset($data[$this->partition['field']])) {
			$field   =   $data[$this->partition['field']];
			switch($this->partition['type']) {
				case 'id':
					// 按照id范围分表
					$step    =   $this->partition['expr'];
					$seq    =   floor($field / $step)+1;
					break;
				case 'year':
					// 按照年份分表
					if(!is_numeric($field)) {
						$field   =   strtotime($field);
					}
					$seq    =   date('Y',$field)-$this->partition['expr']+1;
					break;
				case 'mod':
					// 按照id的模数分表
					$seq    =   ($field % $this->partition['num'])+1;
					break;
				case 'md5':
					// 按照md5的序列分表
					$seq    =   (ord(substr(md5($field),0,1)) % $this->partition['num'])+1;
					break;
				default :
					if(function_exists($this->partition['type'])) {
						// 支持指定函数哈希
						$fun    =   $this->partition['type'];
						$seq    =   (ord(substr($fun($field),0,1)) % $this->partition['num'])+1;
					}else{
						// 按照字段的首字母的值分表
						$seq    =   (ord($field{0}) % $this->partition['num'])+1;
					}
			}
			return $this->getTableName().'_'.$seq;
		}else{
			// 当设置的分表字段不在查询条件或者数据中
			// 进行联合查询，必须设定 partition['num']
			$tableName  =   array();
			for($i=0;$i<$this->partition['num'];$i++)
			$tableName[] = 'SELECT * FROM '.$this->getTableName().'_'.($i+1);
			$tableName = '( '.implode(" UNION ",$tableName).') AS '.$this->name;
			return $tableName;
		}
	}
}